package com.sevenheaven.segmentcontrol;

import com.chinasoft_ohos.commontools.util.AttrValue;
import com.chinasoft_ohos.commontools.util.TouchHelper;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentState;
import ohos.agp.components.Text;
import ohos.agp.components.element.StateElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import com.sevenheaven.segmentcontrol.utils.ColorListHelper;
import com.sevenheaven.segmentcontrol.utils.Line;
import com.sevenheaven.segmentcontrol.utils.Misc;

import timber.log.Timber;

public class SegmentControl extends Component {
    private String[] mTexts = new String[]{};
    private Rect[] mCacheBounds;
    private Rect[] mTextBounds;
    private Line[] mSeparateLines;

    private RadiusElement mBackgroundDrawable;
    private RadiusElement mSelectedDrawable;

    private int mCurrentIndex;

    private int mTouchSlop;
    private boolean inTapRegion;
    private float mStartX;
    private float mStartY;
    private float mCurrentX;
    private float mCurrentY;

    private int mHorizonGap;
    private int mVerticalGap;

    /**
     * 外边框的width
     */
    private int mBoundWidth = 4;
    /**
     * 内边框的width
     */
    private int mSeparatorWidth = mBoundWidth / 2;

    private int mSingleChildWidth;
    private int mSingleChildHeight;

    private Paint mPaint = new Paint();

    private int mTextSize;
    private boolean mTextBold;
    private StateElement mBackgroundColors;
    private StateElement mTextColors;
    private int mCornerRadius;

    private RgbColor DEFAULT_SELECTED_COLOR = RgbColor.fromArgbInt(0xFF32ADFF);
    private RgbColor DEFAULT_NORMAL_COLOR = RgbColor.fromArgbInt(0xFFFFFFFF);

    private Paint.FontMetrics mCachedFM;

    public enum Direction {
        HORIZONTAL(0), VERTICAL(1);
        int value;

        Direction(int v) {
            value = v;
        }
    }

    private Direction mDirection = Direction.HORIZONTAL;

    public SegmentControl(Context context) {
        super(context);
    }

    public SegmentControl(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public SegmentControl(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init(AttrSet attrSet) {
        initAttr(attrSet);

        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                SegmentControl.this.onTouchEvent(touchEvent);
                return true;
            }
        });

        setEstimateSizeListener(new EstimateSizeListener() {
            @Override
            public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
                Size size = doEstimateSize(widthMeasureSpec, heightMeasureSpec);
                Timber.d("onEstimateSize size=%s", size);

                setEstimatedSize(
                    Component.EstimateSpec.getChildSizeWithMode(size.width, size.width,
                        Component.EstimateSpec.NOT_EXCEED),
                    Component.EstimateSpec.getChildSizeWithMode(size.height, size.height,
                        Component.EstimateSpec.NOT_EXCEED)
                );
                return true;
            }
        });

        addDrawTask(mDrawTask);
    }

    private void initAttr(AttrSet attrSet) {
        Timber.d("initAttr");
        mTexts = AttrValue.get(attrSet, "texts", "").split("\\|");
        mTextBold = AttrValue.get(attrSet, "textBold", false);
        mTextSize = AttrValue.getDimension(attrSet, "textSize", AttrHelper.fp2px(16, getContext()));
        mCornerRadius = AttrValue.getDimension(attrSet, "cornerRadius", AttrHelper.fp2px(5, getContext()));
        mDirection = Direction.values()[AttrValue.get(attrSet, "orientation", 0)];
        mHorizonGap = AttrValue.getDimension(attrSet, "horizonGap", 0);
        mVerticalGap = AttrValue.getDimension(attrSet, "verticalGap", 0);

        int gap = AttrValue.getDimension(attrSet, "gaps", AttrHelper.vp2px(2, getContext()));
        if (mHorizonGap == 0) {
            mHorizonGap = gap;
        }
        if (mVerticalGap == 0) {
            mVerticalGap = gap;
        }
        Timber.d("horizonGap=%d, verticalGap=%d", mHorizonGap, mVerticalGap);

        DEFAULT_NORMAL_COLOR = AttrValue.get(attrSet, "normalColor", DEFAULT_NORMAL_COLOR);
        DEFAULT_SELECTED_COLOR = AttrValue.get(attrSet, "selectedColor", DEFAULT_SELECTED_COLOR);

        mBackgroundColors = AttrValue.get(attrSet, "backgroundColors",
            ColorListHelper.makeWithNormalAndSelect(DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR));
        mTextColors = AttrValue.get(attrSet, "textColors",
            ColorListHelper.makeWithNormalAndSelect(
                new RgbColor(DEFAULT_SELECTED_COLOR), new RgbColor(DEFAULT_NORMAL_COLOR))
        );

        mBoundWidth = AttrValue.getDimension(attrSet, "boundWidth", mBoundWidth);
        mSeparatorWidth = AttrValue.getDimension(attrSet, "separatorWidth", mSeparatorWidth);

        mBackgroundDrawable = new RadiusElement(true);
        mBackgroundDrawable.setCornerRadius(mCornerRadius);
        mBackgroundDrawable.setStroke(mBoundWidth, getSelectedBGColor());
        mBackgroundDrawable.setRgbColor(getNormalBGColor());
        setBackground(mBackgroundDrawable);

        mSelectedDrawable = new RadiusElement(false);
        mSelectedDrawable.setRgbColor(getSelectedBGColor());

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(mTextSize);
        mPaint.setFakeBoldText(mTextBold);
        mCachedFM = mPaint.getFontMetrics();

        // fixme: read touchSlop from framework
        int touchSlop = AttrHelper.vp2px(2, getContext());
        mTouchSlop = touchSlop * touchSlop;
        inTapRegion = false;
    }

    /**
     * 设置文本
     *
     * @param texts
     */
    public void setText(String... texts) {
        mTexts = texts;
        invalidate();
    }

    /**
     * 设置文字颜色
     *
     * @param color 需要设置的颜色
     */
    public void setSelectedTextColors(StateElement color) {
        mTextColors = color;
        invalidate();
    }

    /**
     * 设置背景颜色
     *
     * @param colors 颜色
     */
    public void setColors(StateElement colors) {
        mBackgroundColors = colors;

        if (mBackgroundDrawable != null) {
            mBackgroundDrawable.setStroke(mBoundWidth, getSelectedBGColor());
            mBackgroundDrawable.setRgbColor(getNormalBGColor());
        }

        if (mSelectedDrawable != null) {
            mSelectedDrawable.setRgbColor(getSelectedBGColor());
        }

        invalidate();
    }

    public void setCornerRadius(int cornerRadius) {
        mCornerRadius = cornerRadius;

        if (mBackgroundDrawable != null) {
            mBackgroundDrawable.setCornerRadius(cornerRadius);
        }

        invalidate();
    }

    public void setDirection(Direction direction) {
        Direction tempDirection = mDirection;
        mDirection = direction;

        if (tempDirection != direction) {
            postLayout();
            invalidate();
        }
    }

    public void setTextSize(int sizeInFp) {
        setTextSize(Text.TextSizeType.FP, sizeInFp);
    }

    public void setTextSize(Text.TextSizeType sizeType, int sizeValue) {
        int sizePx = Misc.sizeToPx(sizeType, sizeValue, getContext());
        mPaint.setTextSize(sizePx);

        if (sizeValue != mTextSize) {
            mTextSize = sizeValue;
            mCachedFM = mPaint.getFontMetrics();

            postLayout();
        }
    }

    public void setSelectedIndex(int index) {
        mCurrentIndex = index;

        if (mOnSegmentControlClickListener != null) {
            mOnSegmentControlClickListener.onSegmentControlClick(index);
        }

        invalidate();
    }

    private Size doEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);

        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);

        int width = 0;
        int height = 0;

        Timber.d("doEstimateSize widthSize=%d, heightSize=%d, widthMode=%d, heightMode=%d",
            widthSize, heightSize, widthMode, heightMode);

        if (mTexts != null && mTexts.length > 0) {
            mSingleChildHeight = 0;
            mSingleChildWidth = 0;

            if (mCacheBounds == null || mCacheBounds.length != mTexts.length) {
                mCacheBounds = new Rect[mTexts.length];
            }

            if (mTextBounds == null || mTextBounds.length != mTexts.length) {
                mTextBounds = new Rect[mTexts.length];
                mSeparateLines = new Line[mTexts.length];
            }

            for (int i = 0; i < mTexts.length; i++) {
                String text = mTexts[i];

                if (text != null) {
                    if (mTextBounds[i] == null) {
                        mTextBounds[i] = new Rect();
                    }

                    mTextBounds[i].modify(mPaint.getTextBounds(text));

                    if (mSingleChildWidth < mTextBounds[i].getWidth() + mHorizonGap * 2) {
                        mSingleChildWidth = mTextBounds[i].getWidth() + mHorizonGap * 2;
                    }
                    if (mSingleChildHeight < mTextBounds[i].getHeight() + mVerticalGap * 2) {
                        mSingleChildHeight = mTextBounds[i].getHeight() + mVerticalGap * 2;
                    }

                    Timber.d("doEstimateSize i=%d, text=%s, textBound=%s, mSingleChildWidth=%d, mSingleChildHeight=%d",
                        i, mTexts[i], mTextBounds[i], mSingleChildWidth, mSingleChildHeight);
                }
            }

            switch (widthMode) {
                case EstimateSpec.UNCONSTRAINT: // ??
                    if (mDirection == Direction.HORIZONTAL) {
                        if (widthSize <= mSingleChildWidth * mTexts.length) {
                            mSingleChildWidth = widthSize / mTexts.length;
                            width = widthSize;
                        } else {
                            width = mSingleChildWidth * mTexts.length;
                        }
                    } else {
                        width = widthSize <= mSingleChildWidth ? widthSize : mSingleChildWidth;
                    }
                    break;
                case EstimateSpec.PRECISE:
                    width = widthSize;
                    break;
                case EstimateSpec.NOT_EXCEED:
                default:
                    if (mDirection == Direction.HORIZONTAL) {
                        width = mSingleChildWidth * mTexts.length;
                    } else {
                        width = mSingleChildWidth;
                    }
                    break;
            }

            switch (heightMode) {
                case EstimateSpec.NOT_EXCEED:
                    if (mDirection == Direction.VERTICAL) {
                        if (heightSize <= mSingleChildHeight * mTexts.length) {
                            mSingleChildHeight = heightSize / mTexts.length;
                            height = heightSize;
                        } else {
                            height = mSingleChildHeight * mTexts.length;
                        }
                    } else {
                        height = heightSize <= mSingleChildHeight ? heightSize : mSingleChildHeight;
                    }
                    break;
                case EstimateSpec.PRECISE:
                    height = heightSize;
                    break;
                case EstimateSpec.UNCONSTRAINT:
                default:
                    if (mDirection == Direction.VERTICAL) {
                        height = mSingleChildHeight * mTexts.length;
                    } else {
                        height = mSingleChildHeight;
                    }

                    break;
            }

            switch (mDirection) {
                case HORIZONTAL:
                    if (mSingleChildWidth != width / mTexts.length) {
                        mSingleChildWidth = width / mTexts.length;
                    }
                    mSingleChildHeight = height;
                    break;
                case VERTICAL:
                    if (mSingleChildHeight != height / mTexts.length) {
                        mSingleChildHeight = height / mTexts.length;
                    }
                    mSingleChildWidth = width;
                    break;
                default:
                    break;
            }

            for (int i = 0; i < mTexts.length; i++) {
                if (mCacheBounds[i] == null) {
                    mCacheBounds[i] = new Rect();
                }

                if (mDirection == Direction.HORIZONTAL) {
                    mCacheBounds[i].left = i * mSingleChildWidth;
                    mCacheBounds[i].top = 0;
                } else {
                    mCacheBounds[i].left = 0;
                    mCacheBounds[i].top = i * mSingleChildHeight;
                }

                mCacheBounds[i].right = mCacheBounds[i].left + mSingleChildWidth;
                mCacheBounds[i].bottom = mCacheBounds[i].top + mSingleChildHeight;

                Timber.d("doEstimateSize i=%d, text=%s, cacheBound=%s", i, mTexts[i], mCacheBounds[i]);
            }
        } else {
            width = widthMode == EstimateSpec.UNCONSTRAINT ? 0 : widthSize;
            height = heightMode == EstimateSpec.UNCONSTRAINT ? 0 : heightSize;
        }

        return new Size(width, height);
    }

    public boolean onTouchEvent(TouchEvent event) {
        float localX = TouchHelper.getLocalX(this, event);
        float localY = TouchHelper.getLocalY(this, event);
        Timber.d("onTouchEvent action=%d, x=%f, y=%f", event.getAction(), localX, localY);

        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                inTapRegion = true;
                mStartX = localX;
                mStartY = localY;
                break;

            case TouchEvent.POINT_MOVE:
                mCurrentX = localX;
                mCurrentY = localY;

                int dx = (int) (mCurrentX - mStartX);
                int dy = (int) (mCurrentY - mStartY);
                int distance = dx * dx + dy * dy;

                if (distance > mTouchSlop) {
                    inTapRegion = false;
                }
                break;

            case TouchEvent.PRIMARY_POINT_UP:
                if (inTapRegion) {
                    int index = 0;
                    if (mDirection == Direction.HORIZONTAL) {
                        index = (int) (mStartX / mSingleChildWidth);
                    } else {
                        index = (int) (mStartY / mSingleChildHeight);
                    }

                    setSelectedIndex(index);
                }
                break;

            default:
                break;
        }

        return true;
    }

    private RgbColor getSelectedTextColor() {
        return ColorListHelper.getByState(mTextColors, new int[]{ComponentState.COMPONENT_STATE_SELECTED});
    }

    private RgbColor getNormalTextColor() {
        return ColorListHelper.getByState(mTextColors, new int[]{ComponentState.COMPONENT_STATE_EMPTY});
    }

    private RgbColor getSelectedBGColor() {
        return ColorListHelper.getByState(mBackgroundColors, new int[]{ComponentState.COMPONENT_STATE_SELECTED});
    }

    private RgbColor getNormalBGColor() {
        return ColorListHelper.getByState(mBackgroundColors, new int[]{ComponentState.COMPONENT_STATE_EMPTY});
    }

    private DrawTask mDrawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            drawBackground(component, canvas);
            doDraw(component, canvas);
        }
    };

    private void drawBackground(Component component, Canvas canvas) {
        mBackgroundDrawable.setBounds(0, 0, getWidth() , getHeight());
        mBackgroundDrawable.drawToCanvas(canvas);
    }

    private void doDraw(Component component, Canvas canvas) {
        Timber.d("doDraw begin");

        if (isEmpty(mTexts)) {
            Timber.d("doDraw text is Empty");
            return;
        }

        for (int i = 0; i < mTexts.length; i++) {
            Timber.d("doDraw i=%d, text=%s, textBound=%s, cacheBound=%s",
                i, mTexts[i], mTextBounds[i], mCacheBounds[i]);

            if (i < mTexts.length - 1) {
                // fixme: 此处的 new 对象暂时没有想到解决办法，因为 Color 没有 modify 方法
                mPaint.setColor(new Color(getSelectedBGColor().asArgbInt()));
                mPaint.setStrokeWidth(mSeparatorWidth);
                drawSeparateLine(canvas, mPaint, i);
            }

            // draw selected drawable
            if (i == mCurrentIndex && mSelectedDrawable != null) {
                int topLeftRadius = 0;
                int topRightRadius = 0;
                int bottomLeftRadius = 0;
                int bottomRightRadius = 0;

                if (mTexts.length == 1) {
                    topLeftRadius = mCornerRadius;
                    bottomLeftRadius = mCornerRadius;
                    topRightRadius = mCornerRadius;
                    bottomRightRadius = mCornerRadius;
                } else {
                    if (mDirection == Direction.HORIZONTAL) {
                        if (i == 0) {
                            topLeftRadius = mCornerRadius;
                            bottomLeftRadius = mCornerRadius;
                        } else if (i == mTexts.length - 1) {
                            topRightRadius = mCornerRadius;
                            bottomRightRadius = mCornerRadius;
                        }
                    } else {
                        if (i == 0) {
                            topLeftRadius = mCornerRadius;
                            topRightRadius = mCornerRadius;
                        } else if (i == mTexts.length - 1) {
                            bottomLeftRadius = mCornerRadius;
                            bottomRightRadius = mCornerRadius;
                        }
                    }
                }

                Timber.d("doDraw radius, tl=%d, tr=%d, bl=%d, br=%d", topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
                mSelectedDrawable.setRadius(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
                mSelectedDrawable.setBounds(mCacheBounds[i].left, mCacheBounds[i].top,
                    mCacheBounds[i].right, mCacheBounds[i].bottom);
                mSelectedDrawable.drawToCanvas(canvas);

                mPaint.setColor(Misc.toUtilsColor(getSelectedTextColor()));
            } else {
                mPaint.setColor(Misc.toUtilsColor(getNormalTextColor()));
            }

            //draw texts
            float baseline = mCacheBounds[i].top + ((mSingleChildHeight - mCachedFM.ascent + mCachedFM.descent) / 2) - mCachedFM.descent;
            canvas.drawText(mPaint,
                mTexts[i],
                mCacheBounds[i].left + (mSingleChildWidth - mTextBounds[i].getWidth()) / 2, baseline);
        }
    }

    // =========================================================
    // OnSegmentControlClickListener
    // =========================================================
    private OnSegmentControlClickListener mOnSegmentControlClickListener;

    public void setOnSegmentControlClickListener(OnSegmentControlClickListener listener) {
        mOnSegmentControlClickListener = listener;
    }

    public OnSegmentControlClickListener getOnSegmentControlClicklistener() {
        return mOnSegmentControlClickListener;
    }

    /**
     * 点击监听接口
     */
    public interface OnSegmentControlClickListener {
        /**
         * 点击处理
         * @param index 点击位置
         */
        void onSegmentControlClick(int index);
    }

    private void drawSeparateLine(Canvas canvas, Paint paint, int index) {
        if (mSeparateLines[index] == null) {
            mSeparateLines[index] = new Line();
        }

        Line line = mSeparateLines[index];
        Rect bound = mCacheBounds[index];

        if (mDirection == Direction.HORIZONTAL) {
            int startX = bound.right;
            line.modify(startX, 0, startX, getHeight());
        } else {
            int endY = mSingleChildHeight * (index + 1);
            line.modify(bound.left, endY, bound.right, endY);
        }

        line.draw(canvas, paint);
    }

    /**
     * 判断数组是否为空
     *
     * @param array 数组
     * @param <T> 数组类型
     * @return 是否为空
     */
    private static <T> boolean isEmpty(T[] array) {
        return array == null || array.length == 0;
    }
}
